use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde::de::DeserializeOwned;
use serde_json::Value;

use http_client::h1::H1Client as Client;
use http_client::{Config, HttpClient, Request};
use http_client::http_types;

pub mod api;
pub mod types;
pub mod storage;
pub mod out;


#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpcRequest {
    pub id: u32,
    pub jsonrpc: String,
    pub method: String,
    pub params: Vec<Value>,
}

impl JsonRpcRequest {
    pub fn with_params(method: &str, params: Vec<Value>) -> JsonRpcRequest {
        JsonRpcRequest {
            id: 1,
            jsonrpc: "2.0".to_owned(),
            method: method.to_owned(),
            params
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpcError {
    pub code: u32,
    pub message: String,
    pub data: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpcResponse<T> {
    pub id: u32,
    pub jsonrpc: String,
    pub result: Option<T>,
    pub error: Option<JsonRpcError>,
}

pub async fn do_request<T>(client: &Client, url: &str, token: &str, req: &JsonRpcRequest) -> Result<JsonRpcResponse<T>> where T: DeserializeOwned {
    let body = http_types::Body::from_json(&req);
    if let Err(e) = body {
        return Err(anyhow::Error::from(e.into_inner()))
    }
    let body = body.unwrap();
    let auth = format!("Bearer {}", &token);
    let mut req = Request::post(url);
    req.set_body(body);
    req.insert_header("Authorization", auth);

    let result = client.send(req).await;
    match result {
        Err(e) => Err(anyhow::Error::from(e.into_inner())),
        Ok(mut rsp) => {
            if rsp.status().is_success() {
                let result: http_types::Result<JsonRpcResponse<T>> = rsp.body_json().await;
                match result {
                    Ok(buf) => {
                        Ok(buf)
                    },
                    Err(e) => Err(anyhow::Error::from(e.into_inner()))
                }
            } else {
                Err(anyhow::Error::msg(format!("http request got error response {}", rsp.status())))
            }
        }
    }
}

pub async fn jsonrpc_request<T>(client: &Client, url: &str, token: &str, req: JsonRpcRequest) -> Result<Option<T>> where T: DeserializeOwned {
    let rsp = do_request( client, url, token, &req).await?;
    if let Some(e) = rsp.error {
        return Err(anyhow::anyhow!("receive error from {} when call {}, {:?}", url, req.method, e))
    }
    Ok(rsp.result)
}